﻿package com.moonshineproject.plugin.selfprofiling {
	import com.moonshineproject.plugin.PluginBase;
	import com.moonshineproject.utils.Pastebin;
	import com.moonshineproject.utils.PastebinEvent;
	import flash.events.Event;
	import flash.events.ProgressEvent;
	import flash.events.ServerSocketConnectEvent;
	import flash.events.TimerEvent;
	import flash.filesystem.File;
	import flash.filesystem.FileMode;
	import flash.filesystem.FileStream;
	import flash.net.ServerSocket;
	import flash.net.Socket;
	import flash.sampler.getLexicalScopes;
	import flash.sampler.getSampleCount;
	import flash.sampler.getSamples;
	import flash.sampler.getSize;
	import flash.sampler.Sample;
	import flash.sampler.sampleInternalAllocs;
	import flash.sampler.setSamplerCallback;
	import flash.sampler.startSampling;
	import flash.sampler.stopSampling;
	import flash.system.System;
	import flash.utils.ByteArray;
	import flash.utils.describeType;
	import flash.utils.setTimeout;
	import flash.utils.Timer;

	public class SelfProfilingPlugin extends PluginBase 
	{
		override public function get name():String			{ return "Self Profiling Plugin"; }
		override public function get author():String		{ return "Moonshine Project Team"; }
		override public function get description():String	{ return "Profiles the IDE for greater good."; }
		override public function get activatedByDefault():Boolean { return false; }
		
		private static const PORT:int = 7125;
		private const PROFILE:String = "prf";
		
		private var file:File = File.applicationDirectory.resolvePath("profiler/saved/raw.txt");
		
		//private static const POLICY_RESPONSE:String = "<cross-domain-policy><allow-access-from domain=\"*\" to-ports=\"*\" /></cross-domain-policy>";
		
		
		private var server:ServerSocket;
		private var client:Socket;
		
		private var remaining:uint = 0;
		private var profiledBytes:ByteArray;
		private var profileItems:Vector.<ProfileItem>;
		private var cmd:int;
		private var raw:Array;
		
		override public function activate():void {
			super.activate();
			registerCommand(PROFILE, prfCommand);
			
			server = new ServerSocket();
			server.bind(PORT);
			server.addEventListener(ServerSocketConnectEvent.CONNECT, clientConnected);
			server.listen();
		}
		override public function deactivate():void {
			super.deactivate();
			stopProfiling();
			unregisterCommand(PROFILE);
			
			if (server) {
				server.removeEventListener(ServerSocketConnectEvent.CONNECT, clientConnected);
				server.close();
				server = null;
			}
		}
		private function prfCommand(args:Array):void {
			if (args[0] == "start") {
				startProfiling();
			} else if (args[0] == "stop") {
				stopProfiling();
			} else if (args[0] == "get") {
				getProfiling();
			} else if (args[0] == "raw") {
				rawProfiling();
			}
		}
		
		public function SelfProfilingPlugin() {
			//ConsoleUtil.linkFunction("prfs", startProfiling);
			//ConsoleUtil.linkFunction("prfg", getProfiling);
			//ConsoleUtil.linkFunction("prfp", pasteProfiling);
			//ConsoleUtil.linkFunction("prfr", rawProfiling);
			//ConsoleUtil.linkFunction("prfpr", pasteRaw);
			//ConsoleUtil.linkFunction("prfsr", saveRaw);
			//ConsoleUtil.linkFunction("prfa", autoProfile);
		}
		
		private function clientConnected(e:ServerSocketConnectEvent):void {
			client = e.socket;
			client.addEventListener(ProgressEvent.SOCKET_DATA, clientData);
			client.addEventListener(Event.CLOSE, clientClose);
			//client.writeUTFBytes(POLICY_RESPONSE);
			//client.writeByte(0);
			//client.flush();
			server.removeEventListener(ServerSocketConnectEvent.CONNECT, clientConnected);
			print("Client connected");
			
			//startProfiling();
			
		}
		
		private function autoProfile():void {
			var timeout:int = 0;
			timeout += 0; setTimeout(startProfiling, timeout);
			//setTimeout(System.gc, timeout+5000);
			timeout += 10000; setTimeout(stopProfiling, timeout);
			timeout += 1000; setTimeout(getProfiling, timeout);
			//timeout += 1000; setTimeout(rawProfiling, timeout);
		}
		
		private function startProfiling():void {
			print("Starting profiling");
			sendByte(1);
		}
		private function stopProfiling():void {
			print("Stopping profiling");
			sendByte(2);
		}
		private function getProfiling():void {
			profileItems = new Vector.<ProfileItem>();
			sendByte(3);
		}
		
		private function sendByte(byte:int):void {
			if (client && client.connected) {
				client.writeByte(byte);
				client.flush();
			}
		}
		
		private function parseProfiling():void {
			//ConsoleUtil.print("PARSE "+profiledBytes.length);
			profiledBytes.position = 0;
			while (profiledBytes.position < profiledBytes.length) {
				var self:Number = profiledBytes.readDouble();
				var cumu:Number = profiledBytes.readDouble();
				var sum:Number = profiledBytes.readDouble();
				//var num:Number = profiledBytes.readDouble();
				var len:uint = profiledBytes.readUnsignedInt();
				//ConsoleUtil.print("PARSE S "+sum+" "+len);
				var func:String = profiledBytes.readUTFBytes(len);
				profileItems.push(new ProfileItem(func, self, cumu, sum));
			}
			print("Profiling data parsed.");
			profileItems.sort(sortBySelf);
			for (var i:int = 0; i < profileItems.length; i++) {
				var p:ProfileItem = profileItems[i];
				//print(p.sum+"\t"+p.num+"\t"+p.name);
				print(p.self+"\t"+p.cumu+"\t"+p.name);
			}
		}
		private function sortBySelf(a:ProfileItem, b:ProfileItem):int {
			if (a.self < b.self) {
				return 1;
			} else if (a.self > b.self) {
				return -1;
			}
			return 0;
		}
		private function sortBySum(a:ProfileItem, b:ProfileItem):int {
			if (a.sum < b.sum) {
				return 1;
			} else if (a.sum > b.sum) {
				return -1;
			}
			return 0;
		}
		private function pasteProfiling():void {
			var msg:String = "";
			for (var i:int = 0; i < profileItems.length; i++) {
				var p:ProfileItem = profileItems[i];
				//msg += p.sum+"\t"+p.num+"\t"+p.name+"\n";
				msg += p.self+"\t"+p.cumu+"\t"+"\t"+p.sum+"\t"+p.name+"\n";
			}
			new Pastebin(msg, "Profile timings").addEventListener(PastebinEvent.SUCCESS, pasteSuccess);
		}
		private function pasteSuccess(e:PastebinEvent):void {
			print("Profile pasted to "+e.message);
		}
		
		private function rawProfiling():void {
			sendByte(4);
		}
		private function parseRaw():void {
			raw = profiledBytes.readObject();
			print("Raw "+raw.length);
			for (var i:int = 0; i < 10; i++) {
				var rawObj:Object = raw[i];
				var time:Number = rawObj["t"];
				var stack:Array = rawObj["s"] as Array;
				//var str:String = time+" "+rawObj["s"];
				var str:String = time+" ";
				var id:int;
				if (rawObj.hasOwnProperty("nd")) {
					id = int(rawObj["nd"]);
					str += "NEW id "+id+" type "+rawObj["c"]+" ";
				} else if (rawObj.hasOwnProperty("dd")) {
					id = int(rawObj["dd"]);
					str += "DEL id "+id+" size "+rawObj["z"]+" ";
				}
				///*
				for (var j:int = 0; j < stack.length; j++) {
					var st:Object = stack[j];
					str += st["n"]+" ";
					/*
					str += "[";
					for (var p:String in st) {
						str += p+" ";
					}
					str += "]";
					*/
				}
				//*/
				print("R "+str);
			}
		}
		private function pasteRaw():void {
			print("Raw "+raw.length);
			var msg:String = "";
			//var l:int = Math.min(100, raw.length);
			var l:int = raw.length;
			for (var i:int = 0; i < l; i++) {
				var rawObj:Object = raw[i];
				var time:Number = rawObj["t"];
				var stack:Array = rawObj["s"] as Array;
				
				//msg += time+"\n";
				//for (var j:int = 0; j < stack.length; j++) {
				
				msg += time+"";
				for (var j:int = 0; j < 1; j++) {
				
					var st:Object = stack[j];
					if (st.hasOwnProperty("f")) {
						msg += "\t"+st["n"]+" ["+st["f"]+":"+st["l"]+"]\n";
					} else {
						msg += "\t"+st["n"]+"\n";
					}
				}
				//msg += "\n";
			}
			print("Paste raw "+msg.length);
			new Pastebin(msg, "Profile raw").addEventListener(PastebinEvent.SUCCESS, pasteSuccess);
		}
		private function saveRaw():void {
			print("Raw "+raw.length);
			var msg:String = "";
			//var l:int = Math.min(100, raw.length);
			var l:int = raw.length;
			for (var i:int = 0; i < l; i++) {
				var rawObj:Object = raw[i];
				var time:Number = rawObj["t"];
				var stack:Array = rawObj["s"] as Array;
				var id:int;
				msg += time+"\n";
				if (rawObj.hasOwnProperty("nd")) {
					id = int(rawObj["nd"]);
					msg += "\tNEW id "+id+" type "+rawObj["c"]+"\n";
				} else if (rawObj.hasOwnProperty("dd")) {
					id = int(rawObj["dd"]);
					msg += "\tDEL id "+id+" size "+rawObj["z"]+"\n";
				}
				for (var j:int = 0; j < stack.length; j++) {
					var st:Object = stack[j];
					if (st.hasOwnProperty("f")) {
						msg += "\t"+st["n"]+" ["+st["f"]+":"+st["l"]+"]\n";
					} else {
						msg += "\t"+st["n"]+"\n";
					}
				}
				msg += "\n";
			}
			print("Save raw "+msg.length);
			var stream:FileStream = new FileStream();
			stream.open(file, FileMode.WRITE);
			stream.writeUTFBytes(msg);
			stream.close();
		}
		
		private function clientData(e:ProgressEvent = null):void {
			//print("DATA B "+client.bytesAvailable+" "+remaining);
			if (remaining > 0) {
				var len:int = Math.min(remaining, client.bytesAvailable);
				client.readBytes(profiledBytes, profiledBytes.length, len);
				remaining -= len;
				//ConsoleUtil.print("Read B "+len+" "+remaining+" "+client.bytesAvailable+" "+profiledBytes.length);
				//client.readBytes(profiledBytes, 0, len);
				//client.readBytes(profiledBytes);
				//ConsoleUtil.print("Read E "+len+" "+remaining+" "+client.bytesAvailable);
				
				if (remaining <= 0) {
					switch (cmd) {
						case 1:	parseProfiling(); break;
						case 2:	parseRaw(); break;
					}
				}
			} else {
				cmd = client.readByte();
				remaining = client.readUnsignedInt();
				profiledBytes = new ByteArray();
			}
			//print("DATA E "+client.bytesAvailable+" "+remaining);
			if (client.bytesAvailable > 0) {
				clientData();
			}
			
			//print("Client data: "+client.readUTFBytes(client.bytesAvailable));
			
			/*
			while (client.bytesAvailable > 0) {
				var sum:Number = client.readDouble();
				var len:Number = client.readUnsignedInt();
				var func:String = client.readUTFBytes(len);
				print("Function "+sum+" "+func);
			}
			*/
			
			//var samples:Object = client.readObject();
			//print("Samples: "+sampleNum);
			//print("Samples "+describeType(samples).toXMLString());
			//for (var p:String in samples) {
				//print("sample: "+p+" "+samples[p]);
			//}
		}
		private function clientClose(e:Event):void {
			print("Client disconnected");
			server.addEventListener(ServerSocketConnectEvent.CONNECT, clientConnected);
			client = null;
		}
		
	}

}